# -*- coding: utf-8 -*-
"""

    taggable.tests
    --------------

    Unit tests.

    :copyright: 2008 by Gustavo Picon
    :license: Apache License 2.0

"""

from django.test import TestCase
from django.db import models, transaction, connection
from taggable.models import Tagged


#~~~~~~~~~~~~~~

class TestObj(models.Model):
    name = models.CharField(max_length=100)

    def __unicode__(self):
        return '<TestObj: %s>' % self.name

    def delete(self):
        self.on_obj_delete()
        super(TestObj, self).delete()


class TestUsr(models.Model):
    name = models.CharField(max_length=100)

    def __unicode__(self):
        return '<TestUsr: %s>' % self.name

    def delete(self):
        self.on_obj_delete()
        super(TestUsr, self).delete()

class TestLoc(models.Model):
    name = models.CharField(max_length=100)

    def __unicode__(self):
        return '<TestLoc: %s>' % self.name

    def delete(self):
        self.on_obj_delete()
        super(TestLoc, self).delete()

class TestTag(models.Model):
    """
    """
    name = models.CharField(max_length=100)

    def __unicode__(self):
        return '<TestTag: %s>' % self.name

    def delete(self):
        self.on_obj_delete()
        super(TestTag, self).delete()


class TestSimpleStats(models.Model):
    tag = models.ForeignKey(TestTag, unique=True)
    count = models.PositiveIntegerField(default=0)

class TestSimpleTagged(Tagged):
    tag = models.ForeignKey(TestTag)
    obj = models.ForeignKey(TestObj)

    taggable_stats = [
        (['tag'], TestSimpleStats),
    ]

    def __unicode__(self):
        return '<TestSimpleTagged %s %s>' % (self.tag, self.obj)

    class Meta:
        unique_together = (('tag', 'obj'),)


class TestStatsTag(models.Model):
    tag = models.ForeignKey(TestTag, unique=True)
    count = models.PositiveIntegerField(default=0)

class TestStatsTagUsr(models.Model):
    tag = models.ForeignKey(TestTag)
    usr = models.ForeignKey(TestUsr)
    count = models.PositiveIntegerField(default=0)

    class Meta:
        unique_together = (('tag', 'usr'),)

class TestStatsTagUsrLoc(models.Model):
    tag = models.ForeignKey(TestTag)
    usr = models.ForeignKey(TestUsr)
    loc = models.ForeignKey(TestLoc)
    count = models.PositiveIntegerField(default=0)

    class Meta:
        unique_together = (('tag', 'usr', 'loc'),)

class TestComplexTagged(Tagged):
    tag = models.ForeignKey(TestTag)
    usr = models.ForeignKey(TestUsr)
    loc = models.ForeignKey(TestLoc)
    obj = models.ForeignKey(TestObj)

    taggable_fields = ('obj', 'tag', 'usr', 'loc')
    taggable_stats = [
        (['tag'], TestStatsTag),
        (['tag', 'usr'], TestStatsTagUsr),
        (['tag', 'usr', 'loc'], TestStatsTagUsrLoc),
    ]

    def __unicode__(self):
        return '<TestSimpleTagged %s %s>' % (self.tag, self.obj)

    class Meta:
        unique_together = (('tag', 'usr', 'loc', 'obj'),)

#~~~~~~~~~~~~~~

class TestBase(TestCase):

    fixtures = ['test_objs.json']

    def check_simple(self):
        cursor = connection.cursor()
        dt = TestSimpleTagged._meta.db_table
        st = TestSimpleStats._meta.db_table

        sql = 'SELECT tag_id, COUNT(1) ' \
              ' FROM %s ' \
              ' GROUP BY tag_id ' \
              ' HAVING COUNT(1) > 0 ORDER BY tag_id ASC' % ((dt,))
        cursor.execute(sql, [])
        expected = cursor.fetchall()

        sql = 'SELECT tag_id, count ' \
              ' FROM %s ' \
              ' WHERE count > 0 ' \
              ' ORDER BY tag_id ASC'% ((st,))
        cursor.execute(sql, [])
        got = cursor.fetchall()

        self.assertEqual(expected, got)

        self.assertEqual(0,
          TestSimpleStats.objects.filter(count__lt=0).count())

        transaction.commit_unless_managed()

    def check_complex(self):
        cursor = connection.cursor()
        dt = TestComplexTagged._meta.db_table
        stt = TestStatsTag._meta.db_table
        stut = TestStatsTagUsr._meta.db_table
        stult = TestStatsTagUsrLoc._meta.db_table

        sql = 'SELECT tag_id, COUNT(1) ' \
              ' FROM %s ' \
              ' GROUP BY tag_id ' \
              ' HAVING COUNT(1) > 0 ORDER BY tag_id ASC' % ((dt,))
        cursor.execute(sql, [])
        expected = cursor.fetchall()

        sql = 'SELECT tag_id, count ' \
              ' FROM %s ' \
              ' WHERE count > 0 ' \
              ' ORDER BY tag_id ASC'% ((stt,))
        cursor.execute(sql, [])
        got = cursor.fetchall()

        self.assertEqual(expected, got)


        sql = 'SELECT tag_id, usr_id, COUNT(1) ' \
              ' FROM %s ' \
              ' GROUP BY tag_id, usr_id ' \
              ' HAVING COUNT(1) > 0 ' \
              ' ORDER BY tag_id ASC, usr_id ASC' % ((dt,))
        cursor.execute(sql, [])
        expected = cursor.fetchall()

        sql = 'SELECT tag_id, usr_id, count ' \
              ' FROM %s ' \
              ' WHERE count > 0 ' \
              ' ORDER BY tag_id ASC, usr_id ASC'% ((stut,))
        cursor.execute(sql, [])
        got = cursor.fetchall()

        self.assertEqual(expected, got)


        sql = 'SELECT tag_id, usr_id, loc_id, COUNT(1) ' \
              ' FROM %s ' \
              ' GROUP BY tag_id, usr_id, loc_id ' \
              ' HAVING COUNT(1) > 0 ' \
              ' ORDER BY tag_id ASC, usr_id ASC, loc_id ASC' % ((dt,))
        cursor.execute(sql, [])
        expected = cursor.fetchall()

        sql = 'SELECT tag_id, usr_id, loc_id, count ' \
              ' FROM %s' \
              ' WHERE count > 0 ' \
              ' ORDER BY tag_id ASC, usr_id ASC, loc_id ASC'% ((stult,))
        cursor.execute(sql, [])
        got = cursor.fetchall()

        self.assertEqual(expected, got)

        # test that the stats table have only counts > 0 (assert that .delete()
        # isn't messed up)
        self.assertEqual(0,
          TestStatsTag.objects.filter(count__lt=0).count())
        self.assertEqual(0,
          TestStatsTagUsr.objects.filter(count__lt=0).count())
        self.assertEqual(0,
          TestStatsTagUsrLoc.objects.filter(count__lt=0).count())

        transaction.commit_unless_managed()
        
    def setUp(self, num=10):
        """ basic test initialization, basically add predictable test data
        """

        self.intervals_simple = [(6, 10), (8, 10), (8, 9), (7, 9), (0, 1),
                                 (2, 5), (5, 10), (2, 9), (5, 6), (4, 8)]
        self.intervals_complex = [
            [(5, 7), (7, 10), (9, 10)],
            [(4, 10), (8, 10), (4, 9)],
            [(4, 7), (1, 6), (0, 8)],
            [(6, 7), (3, 9), (6, 10)],
            [(7, 8), (5, 9), (8, 9)],
            [(4, 7), (8, 10), (3, 7)],
            [(7, 8), (4, 5), (1, 10)],
            [(2, 10), (2, 7), (7, 9)],
            [(1, 7), (1, 2), (9, 10)],
            [(8, 10), (4, 10), (4, 7)]
        ]

class TestBasicTagged(TestBase):
    """ tests that the maintenance of the stats tables work
    """

    fixtures = ['test_objs.json']

    def test_tagged_simple(self):
        """ tests a classical object->tag relation
        """

        count = 0

        self.assertEqual(count, TestSimpleTagged.objects.count())
        self.check_simple()


        objs = TestObj.objects.all()
        tags = TestTag.objects.order_by('name')

        for obj, (start, end) in zip(objs, self.intervals_simple):
            for tag in tags[start:end]:
                TestSimpleTagged.objects.create(tag=tag, obj=obj)

                count += 1
                self.assertEqual(count, TestSimpleTagged.objects.count())
                self.check_simple()


        # tests that removing a queryset works
        for obj in objs[7:9]:
            qset = TestSimpleTagged.objects.filter(obj=obj)
            num = qset.count()
            qset.delete()

            count -= num
            self.assertEqual(count, TestSimpleTagged.objects.count())
            self.check_simple()
        
        # test that removing a single item works correctly
        for tagged in TestSimpleTagged.objects.all()[0:4]:
            tagged.delete()

            count -= 1
            self.assertEqual(count, TestSimpleTagged.objects.count())
            self.check_simple()

        # tests that removing a single related object works
        obj = objs[0]
        qset = TestSimpleTagged.objects.filter(obj=obj)
        num = qset.count()
        obj.delete()

        count -= num
        self.assertEqual(count, TestSimpleTagged.objects.count())
        self.check_simple()

        # tests that removing a queryset of related objects work
        qset = TestSimpleTagged.objects.filter(obj__in=objs[1:4])
        num = qset.count()
        qset.delete()
        
        count -= num
        self.assertEqual(count, TestSimpleTagged.objects.count())
        self.check_simple()

        # removing everything
        TestSimpleTagged.objects.all().delete()
        count = 0
        self.assertEqual(count, TestSimpleTagged.objects.count())
        self.check_simple()


    def test_tagged_complex(self):
        """ tests a multidimentional (4d) relation
        """
        count = 0

        self.assertEqual(count, TestComplexTagged.objects.count())
        self.check_complex()

        objs = TestObj.objects.order_by('name')
        usrs = TestUsr.objects.order_by('name')
        locs = TestLoc.objects.order_by('name')
        tags = TestTag.objects.order_by('name')

        for obj, ((ustart, uend), (lstart, lend), (tstart, tend)) in \
                zip(objs, self.intervals_complex):
            for tag in tags[tstart:tend]:
                for loc in locs[lstart:lend]:
                    for usr in usrs[ustart:uend]:
                        TestComplexTagged.objects.create(tag=tag,
                                                         usr=usr,
                                                         loc=loc,
                                                         obj=obj)
        
                        count += 1
                        self.assertEqual(count,
                            TestComplexTagged.objects.count())
                        self.check_complex()


        # check that removing querysets from the different tagged dependencies
        # work
        def delete_qset(qset, count):
            num = qset.count()
            qset.delete()

            count -= num
            self.assertEqual(count, TestComplexTagged.objects.count())
            self.check_complex()
            return count

        for tag in tags[0:3]:
            count = delete_qset(TestComplexTagged.objects.filter(tag=tag),
                                count)
        for loc in locs[3:5]:
            count = delete_qset(TestComplexTagged.objects.filter(loc=loc),
                                count)
        for usr in usrs[6:8]:
            count = delete_qset(TestComplexTagged.objects.filter(usr=usr),
                                count)
        for obj in objs[8:10]:
            count = delete_qset(TestComplexTagged.objects.filter(obj=obj),
                                count)

        #  check that removing a single item works correctly
        for tagged in TestComplexTagged.objects.all()[0:4]:
            tagged.delete()

            count -= 1
            self.assertEqual(count, TestComplexTagged.objects.count())
            self.check_complex()

        # removing everything
        TestComplexTagged.objects.all().delete()
        count = 0
        self.assertEqual(count, TestComplexTagged.objects.count())
        self.check_complex()
        
#~~
